{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4898b762-b9e5-4090-a7a5-a1efe056fdb5",
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "source": [
    "本课程旨在帮助学生了解如何使用 **XPath** 来解析 HTML 文档，并提取数据。学生将学习 XPath 的基本语法，并通过实际 HTML 示例逐步掌握其应用。\n",
    "\n",
    "---\n",
    "\n",
    "### **1. 什么是 XPath？**\n",
    "\n",
    "#### **1.1 简介**\n",
    "\n",
    "**XPath** 是一种用于查询 XML 和 HTML 文档中节点的语言。它通过定义路径表达式来选择和提取页面中的元素。XPath 广泛用于网络爬虫和网页数据提取中，因为它提供了灵活和强大的查询功能。\n",
    "\n",
    "#### **1.2 XPath 的基础语法**\n",
    "\n",
    "- **`/`**：从根节点选择。\n",
    "- **`//`**：从文档的任何位置选择节点。\n",
    "- **`@`**：选择属性。\n",
    "- **`[]`**：用于索引选择，选择特定位置的元素。\n",
    "- **`text()`**：提取元素的文本内容。\n",
    "\n",
    "---\n",
    "\n",
    "### **2. 环境设置**\n",
    "\n",
    "为了在 Python 中使用 XPath 解析 HTML，建议使用 `lxml` 库。安装步骤如下：\n",
    "\n",
    "#### **2.1 安装依赖**\n",
    "\n",
    "```bash\n",
    "pip install lxml\n",
    "pip install requests\n",
    "```\n",
    "\n",
    "---\n",
    "\n",
    "### **3. XPath 示例**\n",
    "\n",
    "以下为具体的 HTML 示例及其对应的 XPath 代码，通过这些示例，学生可以逐步理解 XPath 的工作原理。\n",
    "\n",
    "---\n",
    "\n",
    "### **示例 1：解析表格中的数据**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "046670cb-1f38-4a6b-9d61-d95654dcea84",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "表头: ['姓名', '年龄']\n",
      "张三的年龄: 25\n",
      "所有姓名: ['姓名', '张三', '李四']\n"
     ]
    }
   ],
   "source": [
    "from lxml import etree\n",
    "\n",
    "# 示例 HTML 代码\n",
    "html_content = '''\n",
    "<html>\n",
    "  <body>\n",
    "    <table>\n",
    "      <tr>\n",
    "        <td>姓名</td>\n",
    "        <td>年龄</td>\n",
    "      </tr>\n",
    "      <tr>\n",
    "        <td>张三</td>\n",
    "        <td>25</td>\n",
    "      </tr>\n",
    "      <tr>\n",
    "        <td>李四</td>\n",
    "        <td>30</td>\n",
    "      </tr>\n",
    "    </table>\n",
    "  </body>\n",
    "</html>\n",
    "'''\n",
    "\n",
    "# 解析 HTML\n",
    "tree = etree.HTML(html_content)\n",
    "\n",
    "# 提取表格头部数据\n",
    "header = tree.xpath('//table/tr[1]/td/text()')\n",
    "print(f\"表头: {header}\")\n",
    "\n",
    "# 提取张三的年龄\n",
    "zhang_age = tree.xpath('//table/tr[2]/td[2]/text()')[0]\n",
    "print(f\"张三的年龄: {zhang_age}\")\n",
    "\n",
    "# 提取所有姓名\n",
    "names = tree.xpath('//table/tr/td[1]/text()')\n",
    "print(f\"所有姓名: {names}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "682b5f56-4637-4e8c-8376-44512d21e78d",
   "metadata": {},
   "source": [
    "**示例 2：解析带有属性的 HTML**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b3c6b8f0-4678-4cd0-a5a1-99768fb4047a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "链接文本: ['Example 1', 'Test Link']\n",
      "链接 URL: ['https://example.com', 'https://test.com']\n"
     ]
    }
   ],
   "source": [
    "from lxml import etree\n",
    "\n",
    "# 示例 HTML 代码\n",
    "html_content = '''\n",
    "<html>\n",
    "  <body>\n",
    "    <a href=\"https://example.com\">Example 1</a>\n",
    "    <a href=\"https://test.com\">Test Link</a>\n",
    "  </body>\n",
    "</html>\n",
    "'''\n",
    "\n",
    "# 解析 HTML\n",
    "tree = etree.HTML(html_content)\n",
    "\n",
    "# 提取所有链接的文本\n",
    "link_texts = tree.xpath('//a/text()')\n",
    "print(f\"链接文本: {link_texts}\")\n",
    "\n",
    "# 提取所有链接的 URL\n",
    "links = tree.xpath('//a/@href')\n",
    "print(f\"链接 URL: {links}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a44e994e-21b6-43bc-aa5d-58bb3e2473df",
   "metadata": {},
   "source": [
    "**示例 3：复杂 HTML 结构**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0ba65589-4d0b-4ac5-b212-dd93351b8806",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "页面标题: 页面标题\n",
      "作者: 作者: 李华\n",
      "列表项: ['第一点', '第二点', '第三点']\n"
     ]
    }
   ],
   "source": [
    "from lxml import etree\n",
    "\n",
    "# 示例 HTML 代码\n",
    "html_content = '''\n",
    "<html>\n",
    "  <body>\n",
    "    <div class=\"content\">\n",
    "      <h1>页面标题</h1>\n",
    "      <div class=\"article\">\n",
    "        <p>这是一段介绍文字。</p>\n",
    "        <p class=\"author\">作者: 李华</p>\n",
    "        <ul>\n",
    "          <li>第一点</li>\n",
    "          <li>第二点</li>\n",
    "          <li>第三点</li>\n",
    "        </ul>\n",
    "      </div>\n",
    "    </div>\n",
    "  </body>\n",
    "</html>\n",
    "'''\n",
    "\n",
    "# 解析 HTML\n",
    "tree = etree.HTML(html_content)\n",
    "\n",
    "# 提取页面标题\n",
    "title = tree.xpath('//h1/text()')[0]\n",
    "print(f\"页面标题: {title}\")\n",
    "\n",
    "# 提取作者信息\n",
    "author = tree.xpath('//p[@class=\"author\"]/text()')[0]\n",
    "print(f\"作者: {author}\")\n",
    "\n",
    "# 提取所有列表项\n",
    "list_items = tree.xpath('//ul/li/text()')\n",
    "print(f\"列表项: {list_items}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0760d99-d3eb-4542-b26a-4027004ad78c",
   "metadata": {},
   "source": [
    "### **4. 综合案例：解析豆瓣电影数据**\n",
    "\n",
    "下面的例子展示如何从豆瓣电影 `Top 250` 页面中提取电影标题和评分。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4804413a-10ed-423a-93c2-296e5a1a74a3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "电影: 肖申克的救赎, 评分: 9.7\n",
      "电影:  / The Shawshank Redemption, 评分: 9.6\n",
      "电影: 霸王别姬, 评分: 9.5\n",
      "电影: 阿甘正传, 评分: 9.5\n",
      "电影:  / Forrest Gump, 评分: 9.4\n",
      "电影: 泰坦尼克号, 评分: 9.4\n",
      "电影:  / Titanic, 评分: 9.5\n",
      "电影: 千与千寻, 评分: 9.4\n",
      "电影:  / 千と千尋の神隠し, 评分: 9.4\n",
      "电影: 这个杀手不太冷, 评分: 9.4\n",
      "电影:  / Léon, 评分: 9.5\n",
      "电影: 美丽人生, 评分: 9.4\n",
      "电影:  / La vita è bella, 评分: 9.3\n",
      "电影: 星际穿越, 评分: 9.2\n",
      "电影:  / Interstellar, 评分: 9.3\n",
      "电影: 盗梦空间, 评分: 9.3\n",
      "电影:  / Inception, 评分: 9.2\n",
      "电影: 楚门的世界, 评分: 9.3\n",
      "电影:  / The Truman Show, 评分: 9.6\n",
      "电影: 辛德勒的名单, 评分: 9.2\n",
      "电影:  / Schindler's List, 评分: 9.3\n",
      "电影: 忠犬八公的故事, 评分: 9.3\n",
      "电影:  / Hachi: A Dog's Tale, 评分: 9.3\n",
      "电影: 海上钢琴师, 评分: 9.2\n",
      "电影:  / La leggenda del pianista sull'oceano, 评分: 9.1\n"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "from lxml import etree\n",
    "\n",
    "# 设置请求的 URL\n",
    "url = \"https://movie.douban.com/top250\"\n",
    "\n",
    "# 定义请求头，模拟浏览器\n",
    "headers = {\n",
    "    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'\n",
    "}\n",
    "\n",
    "# 发送 GET 请求，获取页面内容\n",
    "response = requests.get(url, headers=headers)\n",
    "\n",
    "# 检查请求是否成功\n",
    "if response.status_code == 200:\n",
    "    html_content = response.content  # 获取网页 HTML 内容\n",
    "\n",
    "    # 使用 lxml 的 etree 解析 HTML\n",
    "    tree = etree.HTML(html_content)\n",
    "    \n",
    "    # 使用 XPath 提取电影标题\n",
    "    movie_titles = tree.xpath(\"//div[@class='hd']/a/span[@class='title']/text()\")\n",
    "    \n",
    "    # 提取电影的评分\n",
    "    movie_ratings = tree.xpath(\"//span[@class='rating_num']/text()\")\n",
    "    \n",
    "    # 打印所有提取到的电影标题和评分\n",
    "    for title, rating in zip(movie_titles, movie_ratings):\n",
    "        print(f\"电影: {title}, 评分: {rating}\")\n",
    "else:\n",
    "    print(f\"请求失败，状态码: {response.status_code}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fa84a66-3a16-4393-a044-958177e5c68a",
   "metadata": {},
   "source": [
    "### **5. XPath 与 CSS 选择器的对比**\n",
    "\n",
    "- **XPath**：\n",
    "  - **优点**：XPath 更灵活，可以通过属性、文本、索引等方式进行复杂查询。\n",
    "  - **缺点**：语法稍显复杂。\n",
    "\n",
    "- **CSS 选择器**：\n",
    "  - **优点**：CSS 选择器语法简单，更加直观。\n",
    "  - **缺点**：对于复杂文档结构，CSS 选择器的表达能力有限。\n",
    "\n",
    "---\n",
    "\n",
    "### **6. 课后练习**\n",
    "\n",
    "1. 使用 XPath 提取豆瓣 `Top 250` 页面中的每部电影评分。\n",
    "2. 修改 XPath 表达式，提取每部电影的导演信息。\n",
    "\n",
    "\n",
    "---\n",
    "\n",
    "### **总结**\n",
    "\n",
    "通过本课程，学生将掌握如何使用 XPath 在 HTML 文档中查询和提取数据的技能。XPath 是一个非常强大的工具，尤其在处理复杂 HTML 结构时极具优势。学生可以根据实际的网页结构编写适合的 XPath 表达式，从而高效地提取数据。\n",
    "\n",
    "---\n",
    "\n",
    "### **附录：常用 XPath 表达式**\n",
    "\n",
    "| XPath 表达式                    | 说明                                |\n",
    "| ------------------------------- | ----------------------------------- |\n",
    "| `//div[@class='hd']`            | 选择所有 `class=\"hd\"` 的 `div` 元素 |\n",
    "| `//span[@class='title']/text()` | 提取电影标题的文本内容              |\n",
    "| `//a[@href]`                    | 选择带有 `href` 属性的 `a` 标签     |\n",
    "| `//ul/li/text()`                | 选择所有列表项 `li` 的文本内容      |\n",
    "\n",
    "\n",
    "---\n",
    "\n",
    "### **参考资料**\n",
    "\n",
    "- [XPath 官方文档](https://www.w3.org/TR/xpath/)\n",
    "- [lxml 官方文档](https://lxml.de/xpathxslt.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3832d104-492a-432d-a0e9-8105d521d2a5",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
